//
// ManualKerning
// =============
// A Cinema 4D effector plugin to influence kerning of MoText objects
// By Andreas Block, January 19th 2012
// Mail: open@andreasblock.de
//
// For further information, see manualkerning.h
//
//------------------------------------------------------------------------
// File: maualkerning.cpp  - class implementation
//
#include "c4d.h"
#include "c4d_symbols.h"
#include "c4d_file.h"
#include "c4d_baseeffectorplugin.h"
#include "c4d_falloffplugin.h"
#include "customgui_vector2d.h"
#include "customgui_fontchooser.h"
#if  (API_VERSION >= 13000)
#include "lib_clipmap.h"  // For FontData access
#endif
//#include "..\..\..\modules\mograph\res\description\omograph_text.h"
// TODO use to determine slot?
// MGTEXTOBJECT_EFFECTORLIST_CHAR
// Surely would be possible to check, if we are in this list...
// but that wouldn't mean we are not in another list and are called for that one...

#include "oemanualkerning.h"
#include "manualkerning.h"

char        ManualKerning::g_pFontpath[MANUALKERNING_MAX_PATH_LEN] = {0};
FT_Library  ManualKerning::g_library = NULL;

#ifdef MKDEBUG
void printContainer(const BaseContainer * const bc)
{
  LONG i = 0;

  if (!bc) {
    GePrint("bc NULL");
    return;
  }
  const GeData *gd = bc->GetIndexData(0);
  if (!gd) {
    GePrint("gd NULL");
  }
  while (gd) {
    GePrint("Idx " + LongToString(i) + " ID: " + LongToString(bc->GetIndexId(i)) + " Type: " + LongToString(gd->GetType()));
    i++;
    gd = bc->GetIndexData(i);
  }
  return;
}
#endif

Bool ManualKerning::FontFaceOpen(const Filename &fnFontFile)
{
  String   sFontFile = fnFontFile.GetString();
  char     pFontfile[MANUALKERNING_MAX_FILEPATH_LEN];

  if (g_ed.sFontFile == sFontFile) {
    return TRUE;
  }
  sFontFile.GetCString(pFontfile, sFontFile.GetCStringLen()+1);
  if (g_ed.ftFace) {
    FT_Done_Face(g_ed.ftFace);
    g_ed.ftFace = NULL;
  }
  FT_Error ftError = FT_New_Face(g_library, pFontfile, 0, &g_ed.ftFace); // create face object
  if (ftError) {
    g_ed.sFontFile = "";
    GePrint(GeLoadString(MANUALKERNING_STRING_ERROR_FONT_OPEN, LongToString(ftError), String(pFontfile)));
    return FALSE;
  }
  FT_Set_Char_Size(g_ed.ftFace, 0,  20 * 64, 0, 72); // TODO currently just some values, enough for now
  g_ed.sFontFile = sFontFile;
  return TRUE;
}

#if (API_VERSION >= 13000)
LONG              ManualKerning::g_iNumFonts = 0;
tFontTable        ManualKerning::g_arrFontTable[MANUALKERNING_MAX_FONTS] = {0};

void ManualKerning::FontTableInitR13(void)
{
  if (g_iNumFonts) {
#ifdef MKDEBUG
    GePrint("Number of fonts: " + LongToString(g_iNumFonts)); // TODO remove
#endif
    return;
  }
  BrowseFiles *bf = BrowseFiles::Alloc();
  if (NULL == bf) {
    GePrint(GeLoadString(MANUALKERNING_STRING_ERROR_FILE_BROWSER));
    return;
  }
  bf->Init(Filename(String(g_pFontpath)), BROWSEFILES_SUPPRESSCACHING);
  while ( bf->GetNext() && (g_iNumFonts < MANUALKERNING_MAX_FONTS) ) {
    String sFontpath = String(g_pFontpath) + bf->GetFilename().GetString();

    sFontpath.GetCString(g_arrFontTable[g_iNumFonts].pFilename, sFontpath.GetCStringLen() + 1);
    // TODO: At least on my WinXP system there seem to be no fonts with multiple faces
    //       (or at least none compatible with FreeType2) or I'm doing something wrong...
    //       What would be a good max of faces to check for?
    //       Can fonts have the faces 3 and 7 only?
    //       For now leaving search for up to four faces in, until tested on some other systems.
    for (LONG idxFaces = 0; idxFaces < 4; idxFaces++) {
      FT_Face  ftFace = NULL;
      FT_Error ftError = FT_New_Face(g_library, g_arrFontTable[g_iNumFonts].pFilename, idxFaces, &ftFace); // create face object
      if (!ftError) {
        g_arrFontTable[g_iNumFonts].idxFace = idxFaces;
        g_arrFontTable[g_iNumFonts].iFlagHasKerning = FT_HAS_KERNING(ftFace);
        CopyMem(ftFace->family_name, g_arrFontTable[g_iNumFonts].pNameFamily, String(ftFace->family_name).GetLength() + 1);
        CopyMem(ftFace->style_name, g_arrFontTable[g_iNumFonts].pNameStyle, String(ftFace->style_name).GetLength() + 1);

        String sNameCombined = String(ftFace->family_name) + " " + String(ftFace->style_name);
        sNameCombined.GetCString(g_arrFontTable[g_iNumFonts].pNameCombined, sNameCombined.GetCStringLen() + 1);
        FT_Done_Face(ftFace);
        g_iNumFonts++;
      } else {
        if (0 == idxFaces) {
          GePrint(GeLoadString(MANUALKERNING_STRING_WARNING_FONT_OPEN, LongToString(ftError), String(g_arrFontTable[g_iNumFonts].pFilename)));
        }
        break;
      }
    }
  }
#ifdef MKDEBUG
  GePrint("New FontTable, Number of fonts: " + LongToString(g_iNumFonts));
#endif
  BrowseFiles::Free(bf);
  return;
}

LONG ManualKerning::FontTableFindCombinedR13(const String &sNameCombinedC4D)
{
  LONG idxReturn = -1;
  LONG idxSubstring = -1;

#ifdef MKDEBUG
  GePrint("Find C4D: " + sNameCombinedC4D);
#endif
  // TODO use hash table instead -> sglib.h?
  for (LONG idxFont = 0; idxFont < g_iNumFonts; idxFont++) {
    if (sNameCombinedC4D == String(g_arrFontTable[idxFont].pNameCombined)) {
      idxReturn = idxFont;
#ifdef MKDEBUG
       GePrint("Found FT2 exact match: " + String(g_arrFontTable[idxReturn].pNameFamily) + " (" + String(g_arrFontTable[idxReturn].pNameStyle) + ") - " + String(g_arrFontTable[idxReturn].pFilename));
#endif
      return idxReturn;
    }
    // Carry index of first substring match, to be used later, if no exact match was found
    if (-1 == idxSubstring) {
      LONG iLenMax = String(g_arrFontTable[idxFont].pNameCombined).GetLength();
// TODO also compare other substr (which will lead to pretty fuzzy results)
//      if (sNameCombinedC4D.GetLength() < iLenMax) {
//        iLenMax = sNameCombinedC4D.GetLength();
//      }
      if ( sNameCombinedC4D.Left(iLenMax) ==
           String(g_arrFontTable[idxFont].pNameCombined) ) {
//      if ( sNameCombinedC4D.Left(iLenMax) ==
//           String(g_arrFontTable[idxFont].pNameCombined).Left(iLenMax) ) {
         idxSubstring = idxFont;
       }
    }
  }
  if (-1 == idxReturn) {
    idxReturn = idxSubstring;
  }
#ifdef MKDEBUG
  if (-1 == idxReturn) {
    GePrint("Font not found: " + sNameCombinedC4D);
  } else {
    GePrint("Found FT2 substring: " + String(g_arrFontTable[idxReturn].pNameFamily) + " (" + String(g_arrFontTable[idxReturn].pNameStyle) + ") - " + String(g_arrFontTable[idxReturn].pFilename));
  }
#endif
  return idxReturn;
}

void ManualKerning::FontAutoSetFilenameR13(BaseContainer * const bcData, const BaseContainer * const bcDataMoText, String &sStatus)
{
  GeData         d;
  FontData      *fd;
  BaseContainer  bcFont;
  String         sNameFamilyC4D, sNameStyleC4D, sNameCombinedC4D;
  LONG           iFontTableIdx;

  if (!bcData->GetBool(MANUALKERNING_AUTO_FLAG_ENABLE, PARAM_DEFAULT_AUTO_FLAG_ENABLE)) {
    sStatus = GeLoadString(MANUALKERNING_STRING_AUTO_STATUS_DISABLED);
    goto FASF_CONTINUE_WITHOUT_FONT_SEARCH;
  }
  if (bcData->GetBool(MANUALKERNING_AUTO_FLAG_OVERRIDE, PARAM_DEFAULT_AUTO_FLAG_OVERRIDE)) {
    sStatus = GeLoadString(MANUALKERNING_STRING_AUTO_STATUS_MANUAL);
    goto FASF_CONTINUE_WITHOUT_FONT_SEARCH;
  }
  // Get font family name and stye selected in MoText generator
  if (!bcDataMoText->GetParameter(DescLevel(PRIM_TEXT_FONT), d)) {
    sStatus = GeLoadString(MANUALKERNING_STRING_AUTO_STATUS_UNDETERMINED, MANUALKERNING_DEFAULT_NAME_FAMILY);
    bcData->SetFilename(MANUALKERNING_AUTO_FILENAME, PARAM_DEFAULT_AUTO_FILENAME);
    GePrint(GeLoadString(MANUALKERNING_STRING_ERROR_PRIM_TEXT_TEXT));
    goto FASF_CONTINUE_WITHOUT_FONT_SEARCH;
  }
  fd = (FontData*)d.GetCustomDataType(FONTCHOOSER_DATA);
  if (!fd) {
    sStatus = GeLoadString(MANUALKERNING_STRING_AUTO_STATUS_UNDETERMINED, MANUALKERNING_DEFAULT_NAME_FAMILY);
    bcData->SetFilename(MANUALKERNING_AUTO_FILENAME, PARAM_DEFAULT_AUTO_FILENAME);
    GePrint(GeLoadString(MANUALKERNING_STRING_ERROR_FONT_DATA));
    goto FASF_CONTINUE_WITHOUT_FONT_SEARCH;
  }
  bcFont = fd->GetFont();
  if ( (!GeClipMap::GetFontName(&bcFont, GE_FONT_NAME_FAMILY, &sNameFamilyC4D)) &&
       ( (!GeClipMap::GetDefaultFont(GE_FONT_DEFAULT_SYSTEM, &bcFont)) ||
         (!GeClipMap::GetFontName(&bcFont, GE_FONT_NAME_FAMILY, &sNameFamilyC4D)) ) ) {
    sNameFamilyC4D = MANUALKERNING_DEFAULT_NAME_FAMILY;
    GePrint(GeLoadString(MANUALKERNING_STRING_ERROR_FONT_NAME, MANUALKERNING_DEFAULT_NAME_FAMILY));
  }
  if ( (!GeClipMap::GetFontName(&bcFont, GE_FONT_NAME_STYLE, &sNameStyleC4D)) &&
       ( (!GeClipMap::GetDefaultFont(GE_FONT_DEFAULT_SYSTEM, &bcFont)) ||
         (!GeClipMap::GetFontName(&bcFont, GE_FONT_NAME_STYLE, &sNameStyleC4D)) ) ) {
    sNameStyleC4D = MANUALKERNING_DEFAULT_NAME_STYLE;
    GePrint(GeLoadString(MANUALKERNING_STRING_ERROR_FONT_STYLE, MANUALKERNING_DEFAULT_NAME_STYLE));
  }
  // Map style names
  // Some of the font styles in C4D (or under windows???) are localized,
  // so we need to map these to the names used in FreeType2 library
  for (LONG idxStyleId = 0; idxStyleId < (sizeof(ARR_STYLE_IDS)/sizeof(LONG)); idxStyleId++) {
    LONG iPos = 0;
    const String sStyleLocal = GeLoadString(ARR_STYLE_IDS[idxStyleId]); // TODO move load into init?

    // Assuming there're no double bolds, etc...
    // TODO ? Fails here for e.g. Lucida Sans Demibold, which is Lucida Sans Demibold Roman in FreeType2 (won't fix as Lucida has no kerning info anyway)
    if (sNameStyleC4D.FindFirst(sStyleLocal, &iPos, iPos)) {
      const String sNameStyleFT2 = String(ARR_STYLE_NAMES_FT2[idxStyleId]);

      sNameStyleC4D.Delete(iPos, sStyleLocal.GetLength());
      sNameStyleC4D.Insert(iPos, sNameStyleFT2);
    }
  }
  // If font changed, search font file path in font table
  sNameCombinedC4D = sNameFamilyC4D + " " + sNameStyleC4D;
  if (g_ed.sNameCombined != sNameCombinedC4D) {
    iFontTableIdx = FontTableFindCombinedR13(sNameCombinedC4D);
    if (-1 != iFontTableIdx) {
      bcData->SetFilename(MANUALKERNING_AUTO_FILENAME, Filename(String(g_arrFontTable[iFontTableIdx].pFilename)));
      g_ed.sNameCombined = sNameCombinedC4D;
    } else {
      bcData->SetFilename(MANUALKERNING_AUTO_FILENAME, PARAM_DEFAULT_AUTO_FILENAME);
      g_ed.sNameCombined = "";
      sStatus = GeLoadString(MANUALKERNING_STRING_AUTO_STATUS_NOT_FOUND, MANUALKERNING_DEFAULT_NAME_FAMILY);
    }
  }
FASF_CONTINUE_WITHOUT_FONT_SEARCH:
  return;
}
#endif
Bool ManualKerning::ModifyDDescription(GeListNode *node, Description *description, AtomArray* ar)
{
  BaseContainer * const  bcData = ((BaseList2D*)node)->GetDataInstance();
  if (!bcData) {
    return FALSE;
  }
  const DescID *   const singleid = description->GetSingleDescID();
  DescID                 cid;

  // Hide/Unhide font file selector, depending on C4D version
  cid = DescLevel(MANUALKERNING_AUTO_FILENAME, DTYPE_FILENAME, 0);
  if ( (!singleid) || (cid.IsPartOf(*singleid, NULL)) ) {
    BaseContainer * const bcDesc = description->GetParameterI(cid, NULL);

#if (API_VERSION >= 13000)
    if (bcData->GetBool(MANUALKERNING_AUTO_FLAG_OVERRIDE, PARAM_DEFAULT_AUTO_FLAG_OVERRIDE)) {
      bcDesc->SetBool(DESC_HIDE, FALSE);
    } else {
      bcDesc->SetBool(DESC_HIDE, TRUE);
    }
#else
    bcDesc->SetBool(DESC_HIDE, FALSE);
#endif
  }
  // Hide/Unhide override checkbox, depending on C4D version
  cid = DescLevel(MANUALKERNING_AUTO_FLAG_OVERRIDE, DTYPE_BOOL, 0);
  if ( (!singleid) || (cid.IsPartOf(*singleid, NULL)) ) {
    BaseContainer * const bcDesc = description->GetParameterI(cid, NULL);

#if (API_VERSION >= 13000)
    bcDesc->SetBool(DESC_HIDE, FALSE);
#else
    bcDesc->SetBool(DESC_HIDE, TRUE);
#endif
  }
  // Add rule subgroups
  const LONG iRuleMax = ((g_ed.iNumRules < MANUALKERNING_MAX_RULES) ? g_ed.iNumRules : MANUALKERNING_MAX_RULES);
  for (LONG idxRule = 0; idxRule < iRuleMax; idxRule++) {
    cid = DescLevel(MANUALKERNING_RULES_TEXT_BASE + idxRule, DTYPE_STRING, 0);
	  if (!singleid || cid.IsPartOf(*singleid, NULL)) { // important to check for speedup c4d!
		  BaseContainer bcNew = GetCustomDataTypeDefault(DTYPE_STRING);

      bcNew.SetString(DESC_NAME, GeLoadString(MANUALKERNING_STRING_DESCR_RULES_TEXT));
		  bcNew.SetString(DESC_DEFAULT, PARAM_DEFAULT_RULE_TEXT);
		  if (!description->SetParameter(cid, bcNew, DescLevel(MANUALKERNING_GROUP_RULES))) {
        return TRUE;
      }
    }
    cid = DescLevel(MANUALKERNING_RULES_KERNING_BASE + idxRule, DTYPE_REAL, 0);
	  if (!singleid || cid.IsPartOf(*singleid, NULL)) { // important to check for speedup c4d!
		  BaseContainer bcNew = GetCustomDataTypeDefault(DTYPE_REAL);

		  bcNew.SetString(DESC_NAME, GeLoadString(MANUALKERNING_STRING_DESCR_RULES_KERNING));
      bcNew.SetLong(DESC_CUSTOMGUI, CUSTOMGUI_REALSLIDER);
      bcNew.SetReal(DESC_STEP, RCO 0.001);
		  bcNew.SetReal(DESC_DEFAULT, PARAM_DEFAULT_RULE_KERNING);
      bcNew.SetReal(DESC_MINSLIDER, RCO -1.0);
      bcNew.SetReal(DESC_MAXSLIDER, RCO 1.0);
      bcNew.SetLong(DESC_UNIT, DESC_UNIT_PERCENT);
		  if (!description->SetParameter(cid, bcNew, DescLevel(MANUALKERNING_GROUP_RULES))) {
        return TRUE;
      }
    }
  }
  // Add subgroups for each character
  const LONG iCharsMax = ((g_ed.iNumChars < MANUALKERNING_MAX_CHARS) ? g_ed.iNumChars : MANUALKERNING_MAX_CHARS);
  for (LONG idxChar = 0; idxChar < iCharsMax; idxChar++) {
    cid = DescLevel(MANUALKERNING_CHARS_GROUP_BASE + idxChar, DTYPE_GROUP, 0);
	  if (!singleid || cid.IsPartOf(*singleid, NULL)) { // important to check for speedup c4d!
      BaseContainer bcSubgroup = GetCustomDataTypeDefault(DTYPE_GROUP);

      bcSubgroup.SetString(DESC_NAME, g_ed.sTextDescr.SubStr(idxChar, 1));
      // bcSubgroup.SetLong(DESC_DEFAULT, 1); // Note: May be used to create open group
      if (!description->SetParameter(cid, bcSubgroup, DescLevel(MANUALKERNING_GROUP_CHARS))) {
        return TRUE;
      }
    }
    cid = DescLevel(MANUALKERNING_CHARS_KERNING_BASE + idxChar, DTYPE_REAL, 0);
	  if (!singleid || cid.IsPartOf(*singleid, NULL)) { // important to check for speedup c4d!
		  BaseContainer bcNew = GetCustomDataTypeDefault(DTYPE_REAL);

		  bcNew.SetString(DESC_NAME, GeLoadString(MANUALKERNING_STRING_DESCR_CHARS_KERNING));
      bcNew.SetLong(DESC_CUSTOMGUI, CUSTOMGUI_REALSLIDER);
      bcNew.SetReal(DESC_STEP, RCO 0.001);
		  bcNew.SetReal(DESC_DEFAULT, PARAM_DEFAULT_CHAR_KERNING);
      bcNew.SetReal(DESC_MINSLIDER, RCO -1.0);
      bcNew.SetReal(DESC_MAXSLIDER, RCO 1.0);
      bcNew.SetLong(DESC_UNIT, DESC_UNIT_PERCENT);
		  if (!description->SetParameter(cid, bcNew, DescLevel(MANUALKERNING_CHARS_GROUP_BASE + idxChar))) {
        return TRUE;
      }
    }
    cid = DescLevel(MANUALKERNING_CHARS_SIZE_BASE + idxChar, DTYPE_VECTOR, 0);
	  if (!singleid || cid.IsPartOf(*singleid, NULL)) { // important to check for speedup c4d!
		  BaseContainer bcNew = GetCustomDataTypeDefault(DTYPE_VECTOR);

		  bcNew.SetString(DESC_NAME, GeLoadString(MANUALKERNING_STRING_DESCR_CHARS_SIZE));
		  bcNew.SetVector(DESC_MIN, Vector(RCO 0.001));
      bcNew.SetVector(DESC_STEP, Vector(RCO 0.001));
		  bcNew.SetVector(DESC_DEFAULT, PARAM_DEFAULT_CHAR_SIZE);
      bcNew.SetLong(DESC_UNIT, DESC_UNIT_PERCENT);
		  if (!description->SetParameter(cid, bcNew, DescLevel(MANUALKERNING_CHARS_GROUP_BASE + idxChar))) {
        return TRUE;
      }
    }
    cid = DescLevel(MANUALKERNING_CHARS_ROTATION_BASE + idxChar, DTYPE_VECTOR, 0);
	  if (!singleid || cid.IsPartOf(*singleid, NULL)) { // important to check for speedup c4d!
		  BaseContainer bcNew = GetCustomDataTypeDefault(DTYPE_VECTOR);

		  bcNew.SetString(DESC_NAME, GeLoadString(MANUALKERNING_STRING_DESCR_CHARS_ROTATION));
      bcNew.SetLong(DESC_UNIT, DESC_UNIT_DEGREE);
		  bcNew.SetVector(DESC_STEP, Vector(RCO 0.001));
		  bcNew.SetVector(DESC_DEFAULT, PARAM_DEFAULT_CHAR_ROTATION);
		  if (!description->SetParameter(cid, bcNew, DescLevel(MANUALKERNING_CHARS_GROUP_BASE + idxChar))) {
        return TRUE;
      }
    }
    cid = DescLevel(MANUALKERNING_CHARS_OFFSET_BASE + idxChar, DTYPE_VECTOR, 0);
	  if (!singleid || cid.IsPartOf(*singleid, NULL)) { // important to check for speedup c4d!
		  BaseContainer bcNew = GetCustomDataTypeDefault(DTYPE_VECTOR);

		  bcNew.SetString(DESC_NAME, GeLoadString(MANUALKERNING_STRING_DESCR_CHARS_OFFSET));
      bcNew.SetLong(DESC_UNIT, DESC_UNIT_PERCENT);
		  bcNew.SetVector(DESC_MIN, Vector(RCO -1.0));
		  bcNew.SetVector(DESC_MAX, Vector(RCO 1.0));
		  bcNew.SetVector(DESC_STEP, Vector(RCO 0.001));
		  bcNew.SetVector(DESC_DEFAULT, PARAM_DEFAULT_CHAR_OFFSET);
      bcNew.SetLong(DESC_CUSTOMGUI, CUSTOMGUI_VECTOR2D);
		  if (!description->SetParameter(cid, bcNew, DescLevel(MANUALKERNING_CHARS_GROUP_BASE + idxChar))) {
        return TRUE;
      }
    }
  }
	return TRUE;
}

Bool ManualKerning::GetDEnabling(GeListNode *node, const DescID &id, const GeData &t_data, DESCFLAGS_ENABLE flags, const BaseContainer *itemdesc)
{
	const BaseContainer &bcData = *((BaseObject*)node)->GetDataInstance();
	switch (id[0].id)
	{
		case MANUALKERNING_AUTO_FLAG_OVERRIDE: // fall through
		case MANUALKERNING_AUTO_FILENAME: // fall through
		case MANUALKERNING_AUTO_KERNING:
			return bcData.GetBool(MANUALKERNING_AUTO_FLAG_ENABLE, PARAM_DEFAULT_AUTO_FLAG_ENABLE);
	}
  return SUPER::GetDEnabling(node, id, t_data, flags, itemdesc);
}

Bool ManualKerning::Message(GeListNode *node, LONG type, void *t_data)
{
  BaseObject * const     obj = static_cast<BaseObject*>(node);
  BaseContainer * const  bcData = obj->GetDataInstance();

  // Button commands
  if (type == MSG_DESCRIPTION_COMMAND) {
    const DescriptionCommand * const dc = (DescriptionCommand*)t_data;

    if (dc->id == MANUALKERNING_CHARS_BUTTON_RESET) {
      // TODO need to care for undo here? Seems to work without though...
      for (LONG idxArrays = 0;
           idxArrays < ((g_ed.iNumChars < MANUALKERNING_MAX_CHARS) ? g_ed.iNumChars : MANUALKERNING_MAX_CHARS);
           idxArrays++) {
        bcData->SetReal(MANUALKERNING_CHARS_KERNING_BASE + idxArrays, PARAM_DEFAULT_CHAR_KERNING);
        bcData->SetVector(MANUALKERNING_CHARS_SIZE_BASE + idxArrays, PARAM_DEFAULT_CHAR_SIZE);
        bcData->SetVector(MANUALKERNING_CHARS_OFFSET_BASE + idxArrays, PARAM_DEFAULT_CHAR_OFFSET);
        bcData->SetVector(MANUALKERNING_CHARS_ROTATION_BASE + idxArrays, PARAM_DEFAULT_CHAR_ROTATION);
      }
    }
  }
  return SUPER::Message(node, type, t_data);
}

void ManualKerning::GetDimension(BaseObject* op, Vector* mp, Vector* rad)
{
  *mp = Vector(RCO 0.0);
  *rad = Vector(RCO 0.0);
  return;
}

LONG ManualKerning::GetEffectorFlags(void)
{
  // TODO rather unsure about these,
  //      but it would be possible to switch off EFFECTORFLAGS_HASFALLOFF here
  return EFFECTORFLAGS_HASFALLOFF | EFFECTORFLAGS_TIMEDEPENDENT; // | EFFECTORFLAGS_CAMERADEPENDENT;
}

Bool ManualKerning::InitEffector(GeListNode *node)
{
	if (!node) {
    return FALSE;
  }
  // Initialize FreeType2 lib (if not done before)
  // Note: This handle will only be closed in PluginEnd()
  GET_FONT_PATH();
  if (!g_library) {
    FT_Error ftError = FT_Init_FreeType(&g_library);
    if (ftError) {
      return FALSE;
    }
  }
  BaseObject * const op = (BaseObject*)node;
	if (!op) {
    return FALSE;
  }
	BaseContainer * const bcData = op->GetDataInstance();
	if (!bcData) {
    return FALSE;
  }
#ifdef MKDEBUG
  GePrint("InitEffector");
#endif
  g_ed.iNumChars = 0;
  g_ed.ftFace = NULL;
  bcData->SetBool(MANUALKERNING_AUTO_FLAG_ENABLE, PARAM_DEFAULT_AUTO_FLAG_ENABLE);
  bcData->SetBool(MANUALKERNING_AUTO_FLAG_OVERRIDE, PARAM_DEFAULT_AUTO_FLAG_OVERRIDE);
  bcData->SetReal(MANUALKERNING_AUTO_KERNING, PARAM_DEFAULT_AUTO_KERNING);
  bcData->SetFilename(MANUALKERNING_AUTO_FILENAME, PARAM_DEFAULT_AUTO_FILENAME);
  bcData->SetLong(MANUALKERNING_RULES_NUM, PARAM_DEFAULT_RULES_NUM);
  bcData->SetBool(MANUALKERNING_OPTION_FLAG_OFFSET_INCREMENTAL, PARAM_DEFAULT_OPTION_FLAG_OFFSET_INCREMENTAL);
  bcData->SetBool(MANUALKERNING_OPTION_FLAG_SIZE_INCREMENTAL, PARAM_DEFAULT_OPTION_FLAG_SIZE_INCREMENTAL);
  bcData->SetBool(MANUALKERNING_OPTION_FLAG_ROTATION_INCREMENTAL, PARAM_DEFAULT_OPTION_FLAG_ROTATION_INCREMENTAL);
  bcData->SetBool(MANUALKERNING_OPTION_FLAG_LINEBREAK, PARAM_DEFAULT_OPTION_FLAG_LINEBREAK);
  // Preinitialize only those parameters defaulting different from zero
  // As I have problems getting GUI and default value in sync on those
  for (LONG idxChar = 0; idxChar < MANUALKERNING_MAX_CHARS; idxChar++) {
    bcData->SetVector(MANUALKERNING_CHARS_SIZE_BASE + idxChar, PARAM_DEFAULT_CHAR_SIZE);
  }
  // Some rule examples
  for (LONG idxRule = 0; idxRule < sizeof(ARR_RULE_DEFAULTS)/sizeof(char[16]); idxRule++) {
    bcData->SetString(MANUALKERNING_RULES_TEXT_BASE + idxRule, String(ARR_RULE_DEFAULTS[idxRule]));
  }
  FontTableInit();
  return TRUE;
}

void ManualKerning::FreeEffector(GeListNode* node)
{
#ifdef MKDEBUG
  GePrint("Free Effector");
#endif
  if (g_ed.ftFace) {
    FT_Done_Face(g_ed.ftFace);
    g_ed.ftFace = NULL;
  }
  return;
}

void ManualKerning::InitPoints(BaseObject *op, BaseObject *gen, BaseDocument *doc, EffectorDataStruct *data, MoData *md, BaseThread *thread)
{
  if (gen == op) { // API docs say, this may happen?
#ifdef MKDEBUG
    GePrint("gen == op"); // TODO localize, if this can really happen
#endif
    return;
  }
  if (1019268 != gen->GetType()) { // TODO can't find type definition...
    GePrint(GeLoadString(MANUALKERNING_STRING_WARNING_WRONG_TARGET));
    return;
  }
	BaseContainer *bcDataEffector = op->GetDataInstance();
  if (!bcDataEffector) {
    return;
  }
  BaseContainer *bcDataGenerator = gen->GetDataInstance();
  if (!bcDataGenerator) {
    return;
  }

  String sStatus, sStatusPrefix, sNameFontDisp;

  FontAutoSetFilename(bcDataEffector, bcDataGenerator, sStatus);
  sNameFontDisp = GeLoadString(MANUALKERNING_STRING_FONT_DISPLAY_NONE);
  sStatusPrefix = GeLoadString(MANUALKERNING_STRING_AUTO_STATUS_INACTIVE);
  g_ed.iFlagAutoKerning = FALSE;
  if (bcDataEffector->GetBool(MANUALKERNING_AUTO_FLAG_ENABLE, PARAM_DEFAULT_AUTO_FLAG_ENABLE)) {
    const Filename fnFilename = bcDataEffector->GetFilename(MANUALKERNING_AUTO_FILENAME, PARAM_DEFAULT_AUTO_FILENAME);

    if (FontFaceOpen(fnFilename)) {
      sNameFontDisp = String(g_ed.ftFace->family_name);
      sNameFontDisp += " (" + String(g_ed.ftFace->style_name) + ")";
      if (FT_HAS_KERNING(g_ed.ftFace)) {
        g_ed.iFlagAutoKerning = TRUE;
        sStatusPrefix = GeLoadString(MANUALKERNING_STRING_AUTO_STATUS_ACTIVE);
      } else {
        sNameFontDisp += GeLoadString(MANUALKERNING_STRING_FONT_DISPLAY_NO_KERNING);
      }
    }
  }
  g_ed.fBaseUnit = bcDataGenerator->GetReal(PRIM_TEXT_HEIGHT);
  g_ed.sText = bcDataGenerator->GetString(PRIM_TEXT_TEXT);
  g_ed.sTextDescr = g_ed.sText;
  g_ed.matGenMg = gen->GetMg();
  // Trim white spaces (so the string corresponds with the MoGraph particles)
  for (LONG idxChar = 0; idxChar < g_ed.sTextDescr.GetLength(); idxChar++) {
    if ( (g_ed.sTextDescr[idxChar] == ' ') ||
         (g_ed.sTextDescr[idxChar] == '\n') ||
         (g_ed.sTextDescr[idxChar] == '\r') ) {
      g_ed.sTextDescr.Delete(idxChar, 1);
      idxChar--;
    }
  }
  g_ed.iNumChars = md->GetCount();
  if (g_ed.sTextDescr.GetLength() < g_ed.iNumChars) { // TODO: More particles than characters, shouldn't occurr... remove?
    g_ed.iNumChars = g_ed.sTextDescr.GetLength();
#ifdef MKDEBUG
    GePrint("Note: Capped number of MoParticels (Bug?)"); // TODO: localize?
#endif
  }
  if (g_ed.sTextDescr.GetLength() > g_ed.iNumChars) {
    GePrint(GeLoadString(MANUALKERNING_STRING_WARNING_WRONG_SLOT));
  }
  if (sStatus.Content() > 0) {
    sStatus = sStatusPrefix + " (" + sStatus + ")";
  } else {
    sStatus = sStatusPrefix;
  }
  g_ed.iFlagLinebreak = bcDataEffector->GetBool(MANUALKERNING_OPTION_FLAG_LINEBREAK, PARAM_DEFAULT_OPTION_FLAG_LINEBREAK);
  g_ed.iNumRules = bcDataEffector->GetLong(MANUALKERNING_RULES_NUM, PARAM_DEFAULT_RULES_NUM);
  g_ed.fAutoKerning = bcDataEffector->GetReal(MANUALKERNING_AUTO_KERNING, PARAM_DEFAULT_AUTO_KERNING);
  g_ed.iFlagOffsetIncremental = bcDataEffector->GetBool(MANUALKERNING_OPTION_FLAG_OFFSET_INCREMENTAL, PARAM_DEFAULT_OPTION_FLAG_OFFSET_INCREMENTAL);
  g_ed.iFlagSizeIncremental = bcDataEffector->GetBool(MANUALKERNING_OPTION_FLAG_SIZE_INCREMENTAL, PARAM_DEFAULT_OPTION_FLAG_SIZE_INCREMENTAL);
  g_ed.iFlagRotationIncremental = bcDataEffector->GetBool(MANUALKERNING_OPTION_FLAG_ROTATION_INCREMENTAL, PARAM_DEFAULT_OPTION_FLAG_ROTATION_INCREMENTAL);
  bcDataEffector->SetString(MANUALKERNING_AUTO_STATUS, sStatus);
  bcDataEffector->SetString(MANUALKERNING_AUTO_FONTNAME, sNameFontDisp);
  return;
}

void ManualKerning::ModifyPoints(BaseObject *op, BaseObject *gen, BaseDocument *doc, EffectorDataStruct *data, MoData *md, BaseThread *thread)
{
	if (data->strength == 0.0) {
    return;
  }
  if (gen == op) {
#ifdef MKDEBUG
    GePrint("gen == op");  // TODO localize ?
#endif
    return;
  }
  if (1019268 != gen->GetType()) { // TODO can't find type definition...
    GePrint(GeLoadString(MANUALKERNING_STRING_WARNING_WRONG_TARGET));
    return;
  }
  C4D_Falloff * const falloff = GetFalloff();
  if (!falloff) {
    return;
  }
	const BaseContainer * const bcData = op->GetDataInstance();
  if (!bcData) {
    return;
  }

  FT_UInt         ftGIndex;
  FT_UInt         ftGIndexLast;
   // TODO check other kerning modes (FT_KERNING_UNSCALED seems most appropriate as we are scaling ourselves later)
  FT_Int          ftKerningMode = FT_KERNING_UNSCALED;  // FT_KERNING_DEFAULT; // FT_KERNING_UNFITTED;
  FT_Vector       ftVec = {0, 0};
  Real            fKerning = RCO 0.0;
  Vector          vOffset = Vector(RCO 0.0);
  Vector          vSize = Vector(RCO 1.0);
  Vector          vRotation = Vector(RCO 0.0);
  Real            fFalloff = RCO 0.0;
	Vector          vOffsetGlobal = RCO 0.0;
  LONG            idxChar = 0;
	MDArray<LONG>   mdArrFlag = md->GetLongArray(MODATA_FLAGS);
	MDArray<Real>   mdArrWeight = md->GetRealArray(MODATA_WEIGHT);
	MDArray<Matrix> mdArrMatrix = md->GetMatrixArray(MODATA_MATRIX);

	if (!mdArrMatrix) {
    return;
  }
  ftGIndexLast = FT_Get_Char_Index(g_ed.ftFace, g_ed.sText[0]);
  for (LONG idxArrays = 0; idxArrays < g_ed.iNumChars; idxArrays++) {
    // Skip any whitespace in original string (regardless, if particle is visible or not..)
    // (there are no MoGraph particles for these anyways, but we still need the linebreak info)
    while ( (g_ed.sText[idxChar] == '\n') ||
            (g_ed.sText[idxChar] == '\r') ||
            (g_ed.sText[idxChar] == ' ') ) {
      if ( (g_ed.iFlagLinebreak) &&
           ( (g_ed.sText[idxChar] == '\n') ||
             (g_ed.sText[idxChar] == '\r') ) ) {
        fKerning = RCO 0.0;
        vOffset = Vector(RCO 0.0);
        vSize = Vector(RCO 1.0);
        vRotation = Vector(RCO 0.0);
      }
      idxChar++;
    }
    //If the particle isn't visible, don't calculate
		if (!(mdArrFlag[idxArrays] & MOGENFLAG_CLONE_ON) || (mdArrFlag[idxArrays] & MOGENFLAG_DISABLE)) {
      continue;
    }
		//Multiply into global space
		vOffsetGlobal = mdArrMatrix[idxArrays].off;
		vOffsetGlobal *= g_ed.matGenMg;
		//Sample the falloff
		falloff->Sample(vOffsetGlobal, &fFalloff, TRUE, mdArrWeight[idxArrays]);
		if (fFalloff == 0.0) {
      continue;
    }
    fFalloff *= data->strength;
		//Calculate kerning offset
    if (0 < idxChar) {
      // Automatic kerning
      if (g_ed.iFlagAutoKerning) {
        // TODO This is very basic and can surely be done in a more sophisticated way.
        //      Currently it's only using the unscaled kerning table embedded in a font to get a base offset,
        //      which will be scaled later. But there are all sorts of nifty stuff, that could be done here.
        //      Like automatically handling "T.C. Boyle" situations (where the points need to be kerned differently)...
        ftGIndex = FT_Get_Char_Index(g_ed.ftFace, g_ed.sText[idxChar]);
        FT_Error ftError = FT_Get_Kerning(g_ed.ftFace, ftGIndexLast, ftGIndex, ftKerningMode, &ftVec);
        if (ftError) {
          GePrint(GeLoadString(MANUALKERNING_STRING_ERROR_KERNING));
        }
        const Real fAuto = (Real)ftVec.x / (RCO 40.0 * RCO 64.0); // TODO probably related to font size, current value by trial and error
#ifdef MKDEBUG
 //       GePrint("Kern: " + g_ed.sText.SubStr(idxChar-1, 1) + "/" + g_ed.sText.SubStr(idxChar, 1) + ": " + LongToString(ftVec.x) + " -> " + RealToString(fAuto));
#endif
        fKerning += fAuto * g_ed.fAutoKerning;
        ftGIndexLast = ftGIndex;
      }
      // Rule based kerning
      // If same subrule occurs in several rules, offsets are added
      for (LONG idxRules = 0; idxRules < g_ed.iNumRules; idxRules++) {
        const String sRuleText = bcData->GetString(MANUALKERNING_RULES_TEXT_BASE + idxRules, PARAM_DEFAULT_RULE_TEXT);
        const LONG   iLenRuleText = sRuleText.GetLength();

        for (LONG idxSubrule = 0; idxSubrule <= (iLenRuleText / 3); idxSubrule++) {
          if ( (g_ed.sText[idxChar-1] == sRuleText[3 * idxSubrule]) &&
               (g_ed.sText[idxChar] == sRuleText[(3 * idxSubrule) + 1]) ) {
            fKerning += bcData->GetReal(MANUALKERNING_RULES_KERNING_BASE + idxRules, PARAM_DEFAULT_RULE_KERNING);
          }
        }
      }
    }
    // Manual kerning
    // TODO kicks in strangely, if used with falloff
    if (idxChar < MANUALKERNING_MAX_CHARS) {
      fKerning += bcData->GetReal(MANUALKERNING_CHARS_KERNING_BASE + idxArrays, PARAM_DEFAULT_CHAR_KERNING);
    }
    // Apply kerning
    const Real fBaseUnitFall = g_ed.fBaseUnit * fFalloff;

    mdArrMatrix[idxArrays].off.x += fBaseUnitFall * fKerning;
    if (idxChar < MANUALKERNING_MAX_CHARS) {
      // Apply offset
      if (g_ed.iFlagOffsetIncremental) {
        vOffset += bcData->GetVector(MANUALKERNING_CHARS_OFFSET_BASE + idxArrays, PARAM_DEFAULT_CHAR_OFFSET);
      } else {
        vOffset = bcData->GetVector(MANUALKERNING_CHARS_OFFSET_BASE + idxArrays, PARAM_DEFAULT_CHAR_OFFSET);
      }
      mdArrMatrix[idxArrays].off.x += fBaseUnitFall * vOffset.x;
      mdArrMatrix[idxArrays].off.y += fBaseUnitFall * vOffset.y;
      // Apply rotation
      const Vector vOffsetTemp = mdArrMatrix[idxArrays].off;
      if (g_ed.iFlagRotationIncremental) {
        vRotation += bcData->GetVector(MANUALKERNING_CHARS_ROTATION_BASE + idxArrays, PARAM_DEFAULT_CHAR_ROTATION);
      } else {
        vRotation = bcData->GetVector(MANUALKERNING_CHARS_ROTATION_BASE + idxArrays, PARAM_DEFAULT_CHAR_ROTATION);
      }
#if (API_VERSION < 13000)
      mdArrMatrix[idxArrays] = HPBToMatrix( Mix( MatrixToHPB(mdArrMatrix[idxArrays]),
                                                 MatrixToHPB(mdArrMatrix[idxArrays]) + vRotation,
                                                 fFalloff ) );
#else
      mdArrMatrix[idxArrays] = HPBToMatrix( Mix( MatrixToHPB(mdArrMatrix[idxArrays], ROTATIONORDER_DEFAULT),
                                                 MatrixToHPB(mdArrMatrix[idxArrays], ROTATIONORDER_DEFAULT) + vRotation,
                                                 fFalloff ), ROTATIONORDER_DEFAULT );
#endif
      mdArrMatrix[idxArrays].off = vOffsetTemp;
      // Apply scale
      if (g_ed.iFlagSizeIncremental) {
        vSize ^= bcData->GetVector(MANUALKERNING_CHARS_SIZE_BASE + idxArrays, PARAM_DEFAULT_CHAR_SIZE);
      } else {
        vSize = bcData->GetVector(MANUALKERNING_CHARS_SIZE_BASE + idxArrays, PARAM_DEFAULT_CHAR_SIZE);
      }
      mdArrMatrix[idxArrays].Scale((vSize * fFalloff) + RCO 1.0 - fFalloff);
      // TODO Fix scale/offset change after scene load (e.g. Logo scene) => No Fix, C4D Bug
    }
    idxChar++;
  }
  return;
}

Bool RegisterManualKerning(void)
{
	return RegisterEffectorPlugin(ID_MANUALKERNING,
                                GeLoadString(MANUALKERNING_PLUGIN_NAME),
                                OBJECT_CALL_ADDEXECUTION,
                                ManualKerning::Alloc,
                                "oemanualkerning",
                                AutoBitmap("manualkerning.tif"),
                                0);
}
